package cn.lopr.mvc.frame.servlet;


import cn.lopr.mvc.frame.Constant;
import cn.lopr.mvc.frame.HandlerInterceptorAdapter;
import cn.lopr.mvc.frame.annotaion.*;
import cn.lopr.mvc.frame.base.ParamMapping;
import cn.lopr.mvc.frame.base.RouteValue;
import cn.lopr.mvc.frame.processors.AutowiredProcessor;
import cn.lopr.mvc.frame.processors.ParamProcessor;
import cn.lopr.mvc.frame.processors.RetValProcess;
import cn.lopr.mvc.frame.util.MyBatisUtils;
import cn.lopr.sccs.Properties;
import com.alibaba.druid.util.MySqlUtils;
import com.alibaba.druid.util.StringUtils;
import org.apache.log4j.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.lopr.util.StringUtils.toLowerFirstCase;

public class DispatchServlet extends HttpServlet {

    private static final Logger LOG = Logger.getLogger(DispatchServlet.class);

    private final List<String> classNameList = new ArrayList<>();
    private final AutowiredProcessor autowiredProcessor = new AutowiredProcessor();

    Map<String, Class<?>> routeMaps = new HashMap<>();
    Map<String, Class<?>> serviceMaps = new HashMap<>();
    Map<String, Object> interceptorMaps = new HashMap<>();
    Map<String, RouteValue> handleMaps = new HashMap<>();

    Map<String, Class<?>> iocRepositoryMaps = new HashMap<>();
    Map<String, Object> iocMap = new HashMap<>();
    ParamProcessor processParam = new ParamProcessor();
    RetValProcess retValProcess = new RetValProcess();
    String contextConfigLocation = "contextConfigLocation";
    String encoding;
    String scan_package;


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setCharacterEncoding(this.encoding);
        resp.setCharacterEncoding(this.encoding);
        resp.setContentType("text/html;charset=UTF-8");
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            req.setCharacterEncoding(this.encoding);
            resp.setCharacterEncoding(this.encoding);
            resp.setHeader("Access-Control-Allow-Origin", "*");
            resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception Detail:\n" + Arrays.toString(e.getStackTrace()));
        } finally {
            MyBatisUtils.remove();
        }
    }

    /**
     * 7、运行阶段，进行拦截，匹配
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IllegalAccessException, IOException {

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.substring(contextPath.length()).replaceAll("/+", "/");

        LOG.info(String.format(" Remote address: %s:%s/%s:%s, request url: %s",
                req.getRemoteAddr(), req.getRemotePort(),
                req.getRemoteHost(), req.getRemotePort(), url));

        if (isContainRes(url)) {
            req.getRequestDispatcher(url);
            return;
        }

        if (!this.handleMaps.containsKey(url)) {
            try {
                resp.setStatus(404);
                resp.getWriter().write("404");
                return;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        RouteValue routeValue = this.handleMaps.get(url);

        String beanName = toLowerFirstCase(routeValue.getMethod().getDeclaringClass().getSimpleName());

        Object[] reqParam = processParam.getParam(routeValue, req);
        Object currentServiceBean = iocMap.get(beanName);
        Field[] fields = currentServiceBean.getClass().getDeclaredFields();

        autowiredProcessor.doRuntimeAutowiredWithHttpReqAndHttpResp(fields, currentServiceBean, req, resp);
        autowiredProcessor.doRuntimeAutowiredWithDataSource(fields, currentServiceBean, iocRepositoryMaps, iocMap);

        for (String key : interceptorMaps.keySet()) {
            Object instance = interceptorMaps.get(key);
            HandlerInterceptorAdapter interceptor = (HandlerInterceptorAdapter) instance;
            Field[] interceptorFields = interceptor.getClass().getDeclaredFields();
            autowiredProcessor.doRuntimeAutowiredWithDataSource(interceptorFields, interceptor, iocRepositoryMaps, iocMap);
            if (!interceptor.preHandler(req, resp, routeValue.getMethod())) {
                return;
            }
        }

        Object returnV = null;
        try {
            returnV = routeValue.getMethod().invoke(iocMap.get(beanName), reqParam);
        } catch (InvocationTargetException e) {

            Throwable cause = e.getCause();
            cause.printStackTrace();
            e.printStackTrace();

            try {
                resp.setStatus(500);
                resp.setContentType("text/html");
                resp.getWriter().write(retValProcess.processErrorMessage(cause));

            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } finally {
            for (String key : interceptorMaps.keySet()) {
                Object instance = interceptorMaps.get(key);
                HandlerInterceptorAdapter interceptor = (HandlerInterceptorAdapter) instance;
                interceptor.afterCompletion(req, resp, routeValue.getMethod());
            }
        }

        returnV = retValProcess.processRetVal(returnV, routeValue.getMethod().getReturnType());

        LOG.debug("[INFO-7] method.invoke put {" + iocMap.get(beanName) + "}.");
        LOG.debug("[INFO-7] method.invoke return value {" + returnV + "}.");
        try {
            if (!Void.TYPE.equals(routeValue.getMethod().getReturnType())) {
                resp.getWriter().write(String.valueOf(returnV));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig servletConfig) {
        doInit(servletConfig);
        doScanner(this.scan_package);
        doInstance();
        autowiredProcessor.doAutowiredIoc(iocMap, iocMap);
        autowiredProcessor.doAutowiredInterceptor(interceptorMaps, iocMap);
        //doAutowiredIoc();
        //doAutowiredInterceptor();
        LOG.info("init HandlerMapping");
        initHandlerMapping();
        LOG.info("init success");
        doTestPrintData();
    }

    private void doInit(ServletConfig servletConfig) {
        String contextConfigLocation = servletConfig.getInitParameter(this.contextConfigLocation);
        if (StringUtils.isEmpty(contextConfigLocation)) {
            return;
        }
        Properties application = Properties.loadProperties(contextConfigLocation
                .replace(".properties", ""));
        this.encoding = application.getValue("encoding");
        this.scan_package = application.getValue("scan-package");
        Constant.contextConfigLocation = contextConfigLocation.replace(".properties", "");
    }

    /**
     * 6、打印数据
     */
    private void doTestPrintData() {
        for (String str : classNameList) {
            LOG.info("Scan list: " + str);
        }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            LOG.info("iocMap: " + entry);
        }

        for (Map.Entry<String, RouteValue> entry : handleMaps.entrySet()) {
            StringBuilder sb = new StringBuilder("[");
            if (entry.getValue().isHasParam()) {
                ParamMapping[] paramMappings = entry.getValue().getParamMappings();
                for (ParamMapping paramMapping : paramMappings) {
                    sb.append(paramMapping.getParamName()).append(", ");
                }
                sb.setLength(sb.length() - 2);
            }
            sb.append("]");
            LOG.info(" Route: " + entry.getKey() + "; Param list: " + sb.toString());
        }
    }

    /**
     * 5、初始化 HandlerMapping
     */
    private void initHandlerMapping() {

        if (iocMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Class<?>> serviceMap : serviceMaps.entrySet()) {
            Class<?> routeClass = routeMaps.get(serviceMap.getKey());

            String baseRoute = routeClass.getAnnotation(Route.class).value();

            Method[] routeClassMethods = routeClass.getMethods();
            Method[] serviceClassMethods = serviceMap.getValue().getMethods();

            for (Method routeClassMethod : routeClassMethods) {
                String methodName = routeClassMethod.getName();

                if (methodName.endsWith("/")) {
                    methodName = methodName.substring(0, methodName.length() - 1);
                }
                String route = ("/" + baseRoute + "/" + methodName).replaceAll("//", "/") + ".do";

                for (Method serviceClassMethod : serviceClassMethods) {
                    if (!serviceClassMethod.getName().equals(routeClassMethod.getName())) {
                        continue;
                    }

                    if (routeClassMethod.getParameterCount() == 0) {
                        handleMaps.put(route, new RouteValue(serviceClassMethod));
                        continue;
                    }

                    if (routeClassMethod.isAnnotationPresent(RequestBody.class)) {
                        Class<?> parameterType = routeClassMethod.getParameterTypes()[0];

                        ParamMapping paramMapping = new ParamMapping(parameterType.getSimpleName(), parameterType, null);
                        handleMaps.put(route, new RouteValue(serviceClassMethod, new ParamMapping[]{paramMapping}, true, true));
                        continue;
                    }

                    Parameter[] parameters = routeClassMethod.getParameters();
                    ParamMapping[] paramMappings = new ParamMapping[routeClassMethod.getParameterCount()];
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        paramMappings[i] = new ParamMapping(parameter.getAnnotation(Param.class).paramName(),
                                parameter.getType(), parameter.getAnnotation(Param.class).defaultValue());
                    }
                    handleMaps.put(route, new RouteValue(serviceClassMethod, paramMappings, true));
                }
            }
        }

    }

    /**
     * 3、初始化 IOC 容器，将所有相关的类实例保存到 IOC 容器中
     */
    private void doInstance() {
        if (classNameList.isEmpty()) {
            return;
        }

        try {
            for (String className : classNameList) {
                if (StringUtils.isEmpty(className)) {
                    continue;
                }
                Class<?> clazz = Class.forName(className);
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                if (StringUtils.isEmpty(beanName)) {
                    continue;
                }
                if (clazz.isAnnotationPresent(Repository.class)) {
                    iocRepositoryMaps.put(beanName, clazz);
                    continue;
                }

                if (clazz.isAnnotationPresent(Service.class)) {
                    Service service = clazz.getAnnotation(Service.class);
                    if (!"".equals(service.value())) {
                        beanName = service.value();
                    }
                    Object instance = clazz.newInstance();
                    if (clazz.getInterfaces()[0].isAnnotationPresent(Route.class)) {
                        routeMaps.put(beanName, clazz.getInterfaces()[0]);
                    }

                    serviceMaps.put(beanName, clazz);
                    iocMap.put(beanName, instance);
                    continue;
                }
                if (clazz.isAnnotationPresent(Component.class)) {

                    Class<?>[] interfaces = clazz.getInterfaces();
                    if (interfaces.length != 0) {
                        if (Constant.interceprot.equals(interfaces[0].getSimpleName())) {
                            Object instance = clazz.newInstance();
                            interceptorMaps.put(beanName, instance);
                        }
                        continue;
                    }
                    Object o = clazz.newInstance();
                    iocMap.put(beanName, o);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 2、扫描相关的类
     *
     * @param scanPackage properties --> scan-package
     */
    private void doScanner(String scanPackage) {
        URL resourcePath = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        if (resourcePath == null) {
            return;
        }

        File classPath = new File(resourcePath.getFile().replace("%20", " "));
        for (File file : Objects.requireNonNull(classPath.listFiles())) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());

            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

                String className = (scanPackage + "." + file.getName()).replace(".class", "");
                classNameList.add(className);
            }
        }
    }

    public boolean isContainRes(String str) {
        Pattern p = Pattern.compile(".jsp|.html|.js|.htm|.css|.jpg|.png");
        Matcher m = p.matcher(str);
        return m.find();
    }
}